/*
Pile, a truly cross-platform automatic build tool.
--------------------------------------------------

main.cpp

Copyright Jonathan Dearborn 2009

Licensed under the GNU Public License (GPL)
See COPYING.txt

This file contains main(), which directs the command-line argument handling,
config loading, GUI loading, and interpreter calls.
*/

#include "pile_global.h"
#include "pile_env.h"
#include "pile_config.h"
#include "pile_depend.h"
#include "pile_commands.h"
#include "pile_build.h"
#include "pile_load.h"
#include "pile_ui.h"
#include "string_functions.h"
#include <cstring>

Environment env;
Configuration config;

void printDepends(const string& file);
bool interpret(string filename, Environment& env, Configuration& config);


extern string log_file;

bool isSourceFile(const string& file);


list<string> getLocalSourceFiles()
{
    list<string> result;
    list<string> ls = ioList(".");
    for(list<string>::iterator e = ls.begin(); e != ls.end(); e++)
    {
        if(isSourceFile(*e))
            result.push_back(*e);
    }
    return result;
}



/*
Finds a pilefile, starting with "com.pile", then the first *.pile that it finds.

Takes: -
Returns: string (file name)
*/
string findPileFile()
{
    if(ioExists("com.pile"))
        return "com.pile";
    string file;

    list<string> ls = ioList(".", false, true);

    for(list<string>::iterator e = ls.begin(); e != ls.end(); e++)
    {
        unsigned int dotpos = e->find_last_of(".");
        if(dotpos != string::npos && e->substr(dotpos, string::npos) == ".pile")
        {
            file = *e;
            break;
        }
    }
    return file;
}



// Types of autogenerated Pilefile: 0=Empty, 1=Compile, 2=C++/SDL
void generatePilefile(string name, int type)
{
    string base = ioStripToFile(name);
    if(ioStripToExt(name) != "pile")
        name += ".pile";
    else
    {
        if(name == "com.pile")
            base = "a.out";
        else
        {
            // Strip extension
            unsigned int dot = base.find_last_of('.');
            if(dot != string::npos)
                base = base.substr(0, dot);
        }
    }

    if(ioExists(name))
    {
        UI_print("Cannot create new pilefile.  %s already exists!\n", name.c_str());
        return;
    }

    ioNew(name);
    
    switch(type)
    {
        case 0:
            break;
        case 1:
            {
                const char* s = "if(VARIANTS <> \"default\")\n"
                                "{\n"
                                "    OUTPUT = \"%s\"\n\n"
                                "    SOURCES += ls(\"*.c\")\n\n"
                                "    SOURCES += ls(\"*.cpp\")\n\n"
                                "    //LFLAGS += [\"-lmylib\"]\n\n"
                                "    CFLAGS += [\"-Wall\"]\n\n"
                                "    cpp_compiler.scan(SOURCES)\n\n"
                                "    OBJECTS += cpp_compiler.compile(SOURCES, CFLAGS)\n\n"
                                "    cpp_linker.link(OUTPUT, OBJECTS, LIBRARIES, LFLAGS)\n"
                                "}\n"
                                "if(VARIANTS <> \"stats\")\n"
                                "{\n"
                                "    SOURCES += ls(\"*.c\")\n\n"
                                "    SOURCES += ls(\"*.cpp\")\n\n"
                                "    SOURCES += ls(\"*.h\")\n\n"
                                "    codeStats(SOURCES, \"c++\")\n"
                                "}\n";
                char buf[strlen(s) + base.size() + 1];
                sprintf(buf, s, base.c_str());
                ioAppend(buf, name);
            }
        break;
        case 2:
            {
                const char* s = "if(VARIANTS <> \"default\")\n"
                                "{\n"
                                "    OUTPUT = \"%s\"\n\n"
                                "    SOURCES += ls(\"*.c\")\n\n"
                                "    SOURCES += ls(\"*.cpp\")\n\n"
                                "    LFLAGS += [\"-lSDLmain\", \"-lSDL\"]\n\n"
                                "    CFLAGS += [\"`sdl-config --cflags`\"]\n\n"
                                "    cpp_compiler.scan(SOURCES)\n\n"
                                "    OBJECTS += cpp_compiler.compile(SOURCES, CFLAGS)\n\n"
                                "    cpp_linker.link(OUTPUT, OBJECTS, LIBRARIES, LFLAGS)\n"
                                "}\n"
                                "if(VARIANTS <> \"stats\")\n"
                                "{\n"
                                "    SOURCES += ls(\"*.c\")\n\n"
                                "    SOURCES += ls(\"*.cpp\")\n\n"
                                "    SOURCES += ls(\"*.h\")\n\n"
                                "    codeStats(SOURCES, \"c++\")\n"
                                "}\n";
                char buf[strlen(s) + base.size() + 1];
                sprintf(buf, s, base.c_str());
                ioAppend(buf, name);
            }
        break;
        default:
            {
                const char* s = "if(VARIANTS <> \"default\")\n"
                                "{\n"
                                "    OUTPUT = \"%s\"\n\n"
                                "    SOURCES += ls(\"*.c\")\n\n"
                                "    SOURCES += ls(\"*.cpp\")\n\n"
                                "    //LFLAGS += [\"-lmylib\"]\n\n"
                                "    CFLAGS += [\"-Wall\"]\n\n"
                                "    cpp_compiler.scan(SOURCES)\n\n"
                                "    OBJECTS += cpp_compiler.compile(SOURCES, CFLAGS)\n\n"
                                "    cpp_linker.link(OUTPUT, OBJECTS, LIBRARIES, LFLAGS)\n"
                                "}\n"
                                "if(VARIANTS <> \"stats\")\n"
                                "{\n"
                                "    SOURCES += ls(\"*.c\")\n\n"
                                "    SOURCES += ls(\"*.cpp\")\n\n"
                                "    SOURCES += ls(\"*.h\")\n\n"
                                "    codeStats(SOURCES, \"c++\")\n"
                                "}\n";
                char buf[strlen(s) + base.size() + 1];
                sprintf(buf, s, base.c_str());
                ioAppend(buf, name);
            }
        break;
    }
    
}

void stripWrappingWhitespace(string& s)
{
    size_t p = s.find_first_not_of(" \n\t\r");
    if(p != string::npos)
        s = s.substr(p, string::npos);
    else
        s = "";
    p = s.find_first_of(" \n\t\r");
        s = s.substr(0, p);
}


string getCFlagHistory()
{
    string file = getConfigDir() + "CFLAGS.history";
    ioFileReader r(file);
    if(r.ready())
        return r.getLine();
    return "";
}

void setCFlagHistory(const string& response)
{
    string file = getConfigDir() + "CFLAGS.history";
    ioClear(file);
    string s = response;
    stripWrappingWhitespace(s);
    ioAppend(response, file);
}

string getLFlagHistory()
{
    string file = getConfigDir() + "LFLAGS.history";
    ioFileReader r(file);
    if(r.ready())
        return r.getLine();
    return "";
}

void setLFlagHistory(const string& response)
{
    string file = getConfigDir() + "LFLAGS.history";
    ioClear(file);
    string s = response;
    stripWrappingWhitespace(s);
    ioAppend(response, file);
}

/*
The basic outline:
Create config file if it doesn't exist.
Delete the old log file if it exists.


*/
int main(int argc, char* argv[])
{

    //string pileDirectory = ioGetProgramPath();

    // Create config dir
    ioNewDir(getConfigDir());

    // Set log file
    string configDir = getConfigDir();
    log_file = configDir + "pile_log.txt";
    //log_file = "pile_log.txt";

    // Delete log file
    ioDelete(log_file);

    UI_debug_pile("Starting up...\n");
    UI_debug_pile("Argc = %d\n", argc);
    for(int i = 0; i < argc; i++)
    {
        UI_debug_pile("Argv[%d] = %s\n", i, argv[i]);
    }




    // Load pile.conf
    if(!ioExists(configDir))
        ioNewDir(configDir);


    UI_debug("Loading config.\n");
    loadConfig(configDir, config);
    UI_debug("Done loading config.\n");

    env.loadConfig(config);

    if(config.installPath == "")
    {
        SYS_alert(("Pile's install path has not been set!  Please edit " + configDir + "pile.conf and set the PILE_PATH string to the directory that contains the Pile installation.\n").c_str());
        return 0;
    }
    else
    {
        /*if(!ioExists(addDirSlash(config.installPath) + ioStripToFile(argv[0])))
        {
            UI_error(("Pile's install path has not been set!  Please edit " + configDir + "pile.conf and set the PILE_PATH string to the directory that contains the Pile installation.\n").c_str());
            return 0;
        }*/
    }

    string file;  // pilefile name


    int cleaning = 0;  // Interpret without actions or messages
    bool graphical = false;
    bool promptForNoPilefile = true;
    // Check for graphical flag
    for(int i = 1; i < argc; i++)
    {
        if(string("-g") == argv[i])
        {
            UI_debug("Initializing UI.\n");
            graphical = true;
            if(!UI_init(true, config))
            {
                UI_error("pile GUI failed to initialize.  Exiting...\n");
                return 0;
            }
            UI_print("Pile GUI started.\n\n");
        }
        else if(string("-n") == argv[i])
        {
            promptForNoPilefile = false;
        }
        else if(string("new") == argv[i])
        {
            // Generate a new pilefile
            string name = "com.pile";
            i++;
            if(i < argc)
            {
                name = argv[i];
            }
            int type = 0;
            string typeStr = name;
            for(unsigned int j = 0; j < typeStr.size(); j++)
                typeStr[j] = tolower(typeStr[j]);
            
            i++;
            if(i < argc)
            {
                typeStr = argv[i];
                if(typeStr == "compile" || typeStr == "build" || typeStr == "c" || typeStr == "cpp" || typeStr == "c++")
                    type = 1;
                else if(typeStr == "sdl")
                    type = 2;
            }
            else
            {
                if(typeStr == "compile" || typeStr == "build" || typeStr == "c" || typeStr == "cpp" || typeStr == "c++")
                {
                    type = 1;
                    name = "com.pile";
                }
                else if(typeStr == "sdl")
                {
                    type = 2;
                    name = "com.pile";
                }
            }

            generatePilefile(name, type);
            return 0;
        }
        else if(string("edit") == argv[i])
        {
            string file = "com.pile";
            i++;
            if(i < argc)
            {
                file = argv[i];
                if(file == "pile.conf")
                {
                    file = configDir + "pile.conf";
                }
            }
            edit(file, config);
            return 0;
        }
        else if(string("--version") == argv[i])
        {
            UI_print("pile Version %s\n", getVersion().c_str());
            return 0; // FIXME: Shouldn't always return here.
        }
        else if(string("scan") == argv[i])
        {
            // FIXME: Create/update dependency files (in depends directory?)
            return 0; // FIXME: Shouldn't always return here.
        }
        else if(string("clean") == argv[i])
        {
            if(i+1 < argc && string("all") == argv[i+1])
            {
                cleaning = 1;
                i++;
            }
            else if(i+1 < argc && string("old") == argv[i+1])
            {
                cleaning = 3;
                i++;
            }
            else
            {
                cleaning = 2;
            }
        }
        else if(string("dryrun") == argv[i])
        {
            env.dryRun = true;
        }
        else if(string("--nocompile") == argv[i])
        {
            env.noCompile = true;
        }
        else if(string("--nolink") == argv[i])
        {
            env.noLink = true;
        }
        // Check for .pile file extension ('pile myfile.pile')
        else if(string("pile") == ioStripToExt(argv[i]))
        {
            // Found a pilefile...
            // Change directory first
            ioSetCWD(ioStripToDir(argv[i]));
            file = ioStripToFile(argv[i]);
        }
        else if(string("-v") == argv[i])
        {
            i++;
            if(i >= argc)
                break;
            // Now we grab all the variants from the list here...
            // It could be "var1,var2,..." or "var1, var2, ..."

            list<string> ls = ioExplode(argv[i], ',');
            for(list<string>::iterator e = ls.begin(); e != ls.end(); e++)
            {
                if(*e != "")
                    env.variants.push_back(*e);
            }
        }
        else
        {
            //p
            string arg = argv[i];
            // FIXME: Replace this find() with a smarter one that takes quotes into account
            unsigned int eq = arg.find("=");
            if(eq != string::npos && eq > 0)
            {
                env.variables.insert(make_pair(arg.substr(0, eq), arg.substr(eq+1)));
            }
            else
            {
                UI_warning("pile Warning: Command \"%s\" not found, so it will be ignored.\n", argv[i]);
            }
        }
    }

    UI_processEvents();
    UI_updateScreen();



    if(!graphical)
    {
        UI_init(false, config);
    }

    UI_debug_pile("Current directory: %s", ioGetCWD().c_str());


    // Find the appropriate pilefile
    if(file == "")
    {
            UI_debug_pile("No pilefile specified.  Searching...\n");
            file = findPileFile();
    }

    bool errorFlag = false;
    bool interpreterError = false;
    // If we've found a Pilefile, then we can begin the build.
    if(file != "")
    {
        UI_debug_pile("Found pilefile: %s\n", file.c_str());
        UI_processEvents();
        UI_updateScreen();

        // Interpret the file
        if(!interpret(file, env, config))
            errorFlag = interpreterError = true;
        UI_debug_pile("Done interpreting.\n");

        UI_processEvents();
        UI_updateScreen();

        if(!errorFlag && env.sources.size() > 0)
        {
            // Scan for dependencies
            if(config.useAutoDepend)
            {
                UI_debug_pile("Scanning.\n");
                for(list<string>::iterator e = env.sources.begin(); e != env.sources.end(); e++)
                {
                    recurseIncludes(env.depends, env.fileDataHash, config.includePaths, *e, "");
                    //if(!recurseIncludes(env.depends, env.fileDataHash, config.includePaths, *e, ""))
                    //{
                    //    errorFlag = true;
                    //    break;
                    //}
                }
            }

            if(!env.dryRun && !cleaning && !errorFlag)
            {
                UI_debug_pile("Building and linking.\n");
                if(!env.noCompile && !build(env, config))
                    errorFlag = true;
                if(!errorFlag)
                    if(!env.noLink && !link(config.languages.find("CPP_LINKER_D")->second, env, config))
                        errorFlag = true;
            }
        }

    }
    else  // No Pilefile found
    {
        // Prompt user in order to proceed to build all local source files.
        if(!promptForNoPilefile || UI_prompt(" No Pilefile found here.  Should I try to build all local source files?\n", 1))
        {
            env.sources = getLocalSourceFiles();
            if(env.variables.find("CFLAGS") == env.variables.end())
            {
                string oldInput = getCFlagHistory();
                string question = " Compiler flags?\n";
                if(oldInput != "")
                    question += string(" Leave blank to use: ") + oldInput + "\n";
                string response = UI_promptString(question);
                if(response == "")
                    response = oldInput;
                else
                    setCFlagHistory(response);
                config.cflags += response;
            }

            if(env.variables.find("LFLAGS") == env.variables.end())
            {
                string oldInput = getLFlagHistory();
                string question = " Linker flags?\n";
                if(oldInput != "")
                    question += string(" Leave blank to use: ") + oldInput + "\n";
                string response = UI_promptString(question);
                if(response == "")
                    response = oldInput;
                else
                    setLFlagHistory(response);
                config.lflags += response;
            }

            // Scan for dependencies
            if(config.useAutoDepend)
            {
                for(list<string>::iterator e = env.sources.begin(); e != env.sources.end(); e++)
                {
                    recurseIncludes(env.depends, env.fileDataHash, config.includePaths, *e, "");
                }
            }

            UI_debug_pile("Building and linking.\n");
            if(!env.dryRun && !cleaning && !errorFlag)
            {
                if(!env.noCompile && !build(env, config))
                    errorFlag = true;
                if(!errorFlag && !env.noLink && !link(config.languages.find("CPP_LINKER_D")->second, env, config))
                    errorFlag = true;
            }
        }
    }

    UI_processEvents();
    UI_updateScreen();

    // Cleaning
    // FIXME: This should be the default clean, but there should also be a way to overload it in the Pilefile.
    if(cleaning > 0)
    {
        if(cleaning == 1)
            clean(true, env.sources, config, env.outfile);
        else if(cleaning == 2)
            clean(false, env.sources, config, env.outfile);
        else if(cleaning == 3)
            cleanOld(false, env.sources, config, env.outfile);
        UI_processEvents();
        UI_updateScreen();
    }



    if(errorFlag)
    {
        if(interpreterError)
            UI_error("\nPilefile errors have occurred...\n");
        else
            UI_error("\nBuild errors have occurred...\n");
    }

    if(graphical)
    {
        ui_print = true;
        ui_log_print = false;
        if(errorFlag)
            UI_print("\nPress any key to quit.\n");
        else
            UI_print("\nAll done!  Press any key.\n");
        UI_updateScreen();

        #ifndef PILE_NO_GUI
        if(env.autoDone)
        {
            UI_print("Pile will automatically close in 5 seconds...\n");
            UI_autoDone();
        }
        else
            UI_waitKeyPress();
        #else
        UI_waitKeyPress();
        #endif
    }

    UI_quit();

    return 0;
}
